/*
Copyright 2007 Frank W. Zammetti

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/


package com.omnytex.struts2.results;


import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.util.ValueStack;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import jimm.datavision.Parameter;
import jimm.datavision.Report;
import jimm.datavision.layout.CharSepLE;
import jimm.datavision.layout.DocBookLE;
import jimm.datavision.layout.HTMLLE;
import jimm.datavision.layout.LaTeXLE;
import jimm.datavision.layout.excel.ExcelLE;
import jimm.datavision.layout.pdf.PDFLE;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.xml.sax.InputSource;


/**
 * This class is a Struts2 Result that allows for integration of the open-source
 * reporting tool DataVision into Struts2 applications.
 *
 * @author <a href="mailto:fzammetti@omnytex.com">Frank W. Zammetti</a>
 */
public class DataVisionResult extends StrutsResultSupport {


  /**
   * Constant defining how big the write buffer is when rendering the
   * report to the response.
   */
  private static final int RESPONSE_BUFFER_SIZE = 50000;


  /**
   * Constant for the DataVision layout engine PDFLE.
   */
  private static final String OUTPUT_TYPE_PDF = "pdf";


  /**
   * Constant for the DataVision layout engine DocBookLE.
   */
  private static final String OUTPUT_TYPE_DOCBOOK = "docbook";


  /**
   * Constant for the DataVision layout engine CharSepLE.
   */
  private static final String OUTPUT_TYPE_DELIMITED = "delimited";


  /**
   * Constant for the DataVision layout engine ExcelLE.
   */
  private static final String OUTPUT_TYPE_EXCEL = "excel";


  /**
   * Constant for the DataVision layout engine LaTeXLE.
   */
  private static final String OUTPUT_TYPE_LATEX = "latex";


  /**
   * Constant for the DataVision layout engine HTMLLE.
   */
  private static final String OUTPUT_TYPE_HTML = "html";


  /**
   * Constant for specifying a "manual" data source.
   */
  private static final String DATASOURCE_TYPE_MANUAL = "manual";


  /**
   * Constant for specifying a "create" data source.
   */
  private static final String DATASOURCE_TYPE_CREATE = "create";


  /**
   * Constant for specifying a "managed" data source.
   */
  private static final String DATASOURCE_TYPE_MANAGED = "managed";


  /**
   * Constant for specifying the output should be streamed.
   */
  private static final String OUTPUT_DISPOSITION_INLINE = "inline";


  /**
   * Constant for specifying the output should be downloaded.
   */
  private static final String OUTPUT_DISPOSITION_ATTACHMENT = "attachment";


  /**
   * The default download filename when dvContentDisposition is
   * OUTPUT_DISPOSITION_ATTACHMENT.
   */
  private static final String DEFAULT_OUTPUT_FILENAME = "output";


  /**
   * The default delimiter character when dvOutputType is OUTPUT_TYPE_DELIMITED.
   */
  private static final String DEFAULT_DELIMITER = ",";


  /**
   * This is the initial size of all StringBuffers created.
   */
  private static final int SB_SIZE = 1024;


  /**
   * Log instance.
   */
  private static Log log = LogFactory.getLog(DataVisionResult.class);


  /**
   * Parameter specifying the report template to use.
   */
  private String dvTemplate;


  /**
   * Parameter specifying the comma-separated list of parameters the report
   * uses.
   */
  private String dvParameters;


  /**
   * Parameter specifying the type of output to generate.  Valid values:
   * OUTPUT_TYPE_HTML, OUTPUT_TYPE_PDF, OUTPUT_TYPE_DOCBOOK,
   * OUTPUT_TYPE_DELIMITED, OUTPUT_TYPE_EXCEL, OUTPUT_TYPE_LATEX.
   * Default: OUTPUT_TYPE_HTML.
   */
  private String dvOutputType;


  /**
   * Paramerter specifying whether the output should be streamed back to the
   * client, presumably displayed by the browser using the appropriate plugin,
   * or whether the user should be presented with a download dialog.
   * Valid values: OUTPUT_DISPOSITION_INLINE, OUTPUT_DISPOSITION_ATTACHMENT.
   * Default: OUTPUT_DISPOSITION_INLINE.
   */
  private String dvOutputDisposition;


  /**
    * Parameter specifying the filename to use by default when
    * dvOutputDisposition is OUTPUT_DISPOSITION_ATTACHMENT.
    * Default: DEFAULT_OUTPUT_FILENAME ("output").
   */
  private String dvOutputFilename;


  /**
   * Parameter specifying the delimiter character to use when using the
   * OUTPUT_TYPE_DELIMITED dvOutputType.  Default: DEFAULT_DELIMITER (comma).
   */
  private String dvDelimiter;


  /**
   * Parameter specifying what method will be used to get the DataSource the
   * Report will use.  Valid values: DATASOURCE_TYPE_MANUAL,
   * DATASOURCE_TYPE_CREATE and DATASOURCE_TYPE_MANAGED.
   * Default: DATASOURCE_TYPE_MANUAL.
   */
  private String dvDataSourceType;


  /**
   * Parameter specifying the fully-qualifed JDBC driver class to use to
   * connect to the database when dvDataSourceType is DATASOURCE_TYPE_CREATE.
   */
  private String dvDriverClass;


  /**
   * Parameter specifying the JDBC connection string to use to
   * connect to the database when dvDataSourceType is DATASOURCE_TYPE_CREATE.
   */
  private String dvConnString;


  /**
   * Parameter specifying the username to use to connect to the database when
   * dvDataSoruceType is DATASOURCE_TYPE_CREATE.
   */
  private String dvUsername;


  /**
   * Parameter specifying the password to use to connect to the database when
   * dvDataSoruceType is DATASOURCE_TYPE_CREATE.
   */
  private String dvPassword;


  /**
   * Parameter specifying the context under which dvJNDIName will be found in
   * JNDI when dvDataSourceType is DATASOURCE_TYPE_MANAGED.
   * Default: InitialContext.
   */
  private String dvJNDIContext;


  /**
   * Parameter specifying the name in JNDI to use to get a DataSource when
   * dvDataSourceType is DATASOURCE_TYPE_MANAGED.
   */
  private String dvJNDIName;


  /**
   * Mutator for dvTemplate.
   *
   * @param inDvTemplate New value for dvTemplate.
   */
  public void setDvTemplate(final String inDvTemplate) {

    dvTemplate = inDvTemplate;

  } // End setDvTemplate().


  /**
   * Mutator for dvParameters.
   *
   * @param inDvParameters New value for dvParameters.
   */
  public void setDvParameters(final String inDvParameters) {

    dvParameters = inDvParameters;

  } // End setDvParameters().


  /**
   * Mutator for dvOutputType.
   *
   * @param inDvOutputType New value for dvOutputType.
   */
  public void setDvOutputType(final String inDvOutputType) {

    dvOutputType = inDvOutputType;

  } // End setDvOutputType().


  /**
   * Mutator for dvOutputDisposition.
   *
   * @param inDvOutputDisposition New value for dvOutputType.
   */
  public void setDvOutputDisposition(final String inDvOutputDisposition) {

    dvOutputDisposition = inDvOutputDisposition;

  } // End setDvOutputDisposition().


  /**
   * Mutator for dvOutputFilename.
   *
   * @param inDvOutputFilename New value for dvOutputFilename.
   */
  public void setDvOutputFilename(final String inDvOutputFilename) {

    dvOutputFilename = inDvOutputFilename;

  } // End setDvOutputFilename().


  /**
   * Mutator for dvDelimiter.
   *
   * @param inDvDelimiter New value for dvDelimiter.
   */
  public void setDvDelimiter(final String inDvDelimiter) {

    dvDelimiter = inDvDelimiter;

  } // End setDvDelimiter().


  /**
   * Mutator for dvDataSourceType.
   *
   * @param inDvDataSourceType New value for dvDataSourceType.
   */
  public void setDvDataSourceType(final String inDvDataSourceType) {

    dvDataSourceType = inDvDataSourceType;

  } // End setDvDataSourceType().


  /**
   * Mutator for dvDriverClass.
   *
   * @param inDvDriverClass New value for dvDriverClass.
   */
  public void setDvDriverClass(final String inDvDriverClass) {

    dvDriverClass = inDvDriverClass;

  } // End setDvDriverClass().


  /**
   * Mutator for dvConnString.
   *
   * @param inDvConnString New value for dvConnString.
   */
  public void setDvConnString(final String inDvConnString) {

    dvConnString = inDvConnString;

  } // End setDvConnString().


  /**
   * Mutator for dvUsername.
   *
   * @param inDvUsername New value for dvUsername.
   */
  public void setDvUsername(final String inDvUsername) {

    dvUsername = inDvUsername;

  } // End setDvUsername().


  /**
   * Mutator for dvPassword.
   *
   * @param inDvPassword New value for dvPassword.
   */
  public void setDvPassword(final String inDvPassword) {

    dvPassword = inDvPassword;

  } // End setDvPassword().


  /**
   * Mutator for dvJNDIName.
   *
   * @param inDvJNDIName New value for dvJNDIName.
   */
  public void setDvJNDIName(final String inDvJNDIName) {

    dvJNDIName = inDvJNDIName;

  } // End setDvJNDIName().


  /**
   * Mutator for dvJNDIContext.
   *
   * @param inDvJNDIContext New value for dvJNDIContext.
   */
  public void setDvJNDIContext(final String inDvJNDIContext) {

    dvJNDIContext = inDvJNDIContext;

  } // End setDvJNDIContext().


  /**
   * This is the main execute method where all the work happens.
   *
   * @param  inFinalLocation inFinalLocation.
   * @param  inInvocation    The ActionInvocation associated with the request.
   * @throws Exception       If anything goes wrong.
   */
  protected void doExecute(final String inFinalLocation,
    final ActionInvocation inInvocation) throws Exception {

    log.trace("doExecute() entry");
    log.debug("-------------------------");

    // Get objects we'll need from the environment.
    ActionContext actionContext = inInvocation.getInvocationContext();
    ServletContext servletContext =
      (ServletContext)inInvocation.getInvocationContext().get(SERVLET_CONTEXT);
    HttpServletResponse response =
      (HttpServletResponse)actionContext.get(
        ServletActionContext.HTTP_RESPONSE);
    ValueStack stack = inInvocation.getStack();
    log.debug("Got environment objects");

    // First, for any parameter that doesn't already have a value, try and get
    // it from the ValueStack.
    populateParameters(stack);
    log.debug("Parameters have been populated");

    // Log configuration parameters for debugging.
    if (log.isDebugEnabled()) {
      log.debug("Configuration informaton:");
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvTemplate = ").append(dvTemplate));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvParameters = ").append(dvParameters));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvOutputType = ").append(dvOutputType));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvOutputDisposition = ").append(dvOutputDisposition));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvOutputFilename = ").append(dvOutputFilename));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvDelimiter = ").append(dvDelimiter));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvDataSourceType = ").append(dvDataSourceType));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvDriverClass = ").append(dvDriverClass));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvConnString = ").append(dvConnString));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvUsername = ").append(dvUsername));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvPassword = ").append(dvPassword));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvJNDIContext = ").append(dvJNDIContext));
      log.debug(new StringBuffer(SB_SIZE).append(
        "dvJNDIName = ").append(dvJNDIName));
    }

    // Perform validations on parameters.  If anything fails, an exception wi;;
    // be thrown and execution will be aborted.
    validateParameters();
    log.debug("Config params valid");

    // Next, we need a connection to the database.  Get as specified.
    DataSource ds = getDataSource(stack);
    log.debug("Valid DataSource now available");

    // Create DataVision report objects.
    ByteArrayOutputStream reportOutputStream = new ByteArrayOutputStream();
    Report report =
      createReportObject(ds, response, servletContext, reportOutputStream);
    log.debug("Report object instantiated and configured");

    // If we have a parameter list, create a Map from the values on the Action.
    populateReportParameters(stack, report);

    // Run the report and generate output.
    log.debug("Running report");
    try {
      report.runReport();
      // Set some various headers on the response as required.
      response.setBufferSize(RESPONSE_BUFFER_SIZE);
      response.setContentLength(reportOutputStream.size());
      StringBuffer dispositionHeaderValue = new StringBuffer(SB_SIZE).append(
        dvOutputDisposition.toLowerCase()).append("; filename=\"").append(
          dvOutputFilename);
      if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_PDF)) {
        dispositionHeaderValue.append(".pdf\"");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_EXCEL)) {
        dispositionHeaderValue.append(".xls\"");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DOCBOOK)) {
        dispositionHeaderValue.append(".xml\"");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_LATEX)) {
        dispositionHeaderValue.append(".latex\"");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_HTML)) {
        dispositionHeaderValue.append(".html\"");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DELIMITED)) {
        dispositionHeaderValue.append(".txt\"");
      }
      log.debug(new StringBuffer(SB_SIZE).append(
        "dispositionHeaderValue = ").append(dispositionHeaderValue));
      response.setHeader("Content-disposition",
        dispositionHeaderValue.toString());
      reportOutputStream.writeTo(response.getOutputStream());
    } catch (final Throwable t) {
      throw new Exception(logException(t, "Exception occurred running report",
        "See stack trace that follows to determine root cause of problem"));
    }
    log.debug("Report run complete");

    log.debug("-------------------------");
    log.trace("doExecute() exit");

  } // End execute().


  /**
   * This method is used to populate any parameters which do not have default
   * values from values found on the ValueStack (they may still be null afetr
   * this, bu that's OK, that'll be handled during validation).
   *
   * @param inStack The ValueStack currently in play.
   */
  protected void populateParameters(final ValueStack inStack) {

    log.trace("populateParameters() entry");

    // For any parameter that doesn't have a value, which means it wasn't part
    // of the Result cofigurtion, try to get a value for each from the
    // VaulueStack.
    if (dvOutputType == null) {
      log.debug("dvOutputType null so trying ValueStack");
      dvOutputType = (String)inStack.findValue("dvOutputType");
    }
    if (dvOutputDisposition == null) {
      log.debug("dvOutputDisposition null so trying ValueStack");
      dvOutputDisposition = (String)inStack.findValue("dvOutputDisposition");
    }
    if (dvOutputFilename == null) {
      log.debug("dvOutputFilename null so trying ValueStack");
      dvOutputFilename = (String)inStack.findValue("dvOutputFilename");
    }
    if (dvDelimiter == null) {
      log.debug("dvDelimiter null so trying ValueStack");
      dvDelimiter = (String)inStack.findValue("dvDelimiter");
    }
    if (dvTemplate == null) {
      log.debug("dvTemplate null so trying ValueStack");
      dvTemplate = (String)inStack.findValue("dvTemplate");
    }
    if (dvParameters == null) {
      log.debug("dvParameters null so trying ValueStack");
      dvParameters = (String)inStack.findValue("dvParameters");
    }
    if (dvDataSourceType == null) {
      log.debug("dvDataSourceType null so trying ValueStack");
      dvDataSourceType = (String)inStack.findValue("dvDataSourceType");
    }
    if (dvDriverClass == null) {
      log.debug("dvDriverClass null so trying ValueStack");
      dvDriverClass = (String)inStack.findValue("dvDriverClass");
    }
    if (dvConnString == null) {
      log.debug("dvConnString null so trying ValueStack");
      dvConnString = (String)inStack.findValue("dvConnString");
    }
    if (dvUsername == null) {
      log.debug("dvUsername null so trying ValueStack");
      dvUsername = (String)inStack.findValue("dvUsername");
    }
    if (dvPassword == null) {
      log.debug("dvPassword null so trying ValueStack");
      dvPassword = (String)inStack.findValue("dvPassword");
    }
    if (dvJNDIContext == null) {
      log.debug("dvJNDIContext null so trying ValueStack");
      dvJNDIContext = (String)inStack.findValue("dvJNDIContext");
    }
    if (dvJNDIName == null) {
      log.debug("dvJNDIName null so trying ValueStack");
      dvJNDIName = (String)inStack.findValue("dvJNDIName");
    }

    // Now, for any parameter that is still null, and which has
    // a default value, set that default value.
    if (dvOutputType == null) {
      log.debug("dvOutputType null so setting default");
      dvOutputType = OUTPUT_TYPE_HTML;
    }
    if (dvOutputDisposition == null) {
      log.debug("dvOutputDisposition null so setting default");
      dvOutputDisposition = OUTPUT_DISPOSITION_INLINE;
    }
    if (dvOutputFilename == null) {
      log.debug("dvOutputFilename null so setting default");
      dvOutputFilename = DEFAULT_OUTPUT_FILENAME;
    }
    if (dvDelimiter == null) {
      log.debug("dvDelimiter null so setting default");
      dvDelimiter = DEFAULT_DELIMITER;
    }
    if (dvDataSourceType == null) {
      log.debug("dvDataSourceType null so setting default");
      dvDataSourceType = DATASOURCE_TYPE_MANUAL;
    }

    log.trace("populateParameters() exit");

  } // End populateParameters().


  /**
   * This method is called to validate the Result parameters once they have
   * been completely populateed.
   *
   * @throws Exception If anything goes wrong.
   */
  protected void validateParameters() throws Exception {

    log.trace("validateParameters() entry");

    // Validate dvTemplate.
    if (dvTemplate == null) {
      throw new Exception(logException(null, "dvTemplate invalid",
        new StringBuffer(SB_SIZE).append(
          "No \"dvTemplate\" parameter was present and it could ").append(
            "not be found on the ValueStack").toString()));
    }

    // Validate dvOutputType.
    if (!dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_HTML) &&
      !dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_PDF) &&
      !dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_EXCEL) &&
      !dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DOCBOOK) &&
      !dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DELIMITED) &&
      !dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_LATEX)) {
      throw new Exception(logException(null, "dvOutputType invalid",
        new StringBuffer(SB_SIZE).append(
          "When present the dvOutputType parameter must be one of ").append(
            "the following values: \"").append(OUTPUT_TYPE_HTML).append(
            "\", \"").append(OUTPUT_TYPE_PDF).append("\", \"").append(
            OUTPUT_TYPE_EXCEL).append("\", \"").append(
            OUTPUT_TYPE_DOCBOOK).append("\", \"").append(
            OUTPUT_TYPE_DELIMITED).append("\", \"").append(
            OUTPUT_TYPE_LATEX).append("\"").toString()));
    }

    // Validate dvOutputDisposition.
    if (!dvOutputDisposition.equalsIgnoreCase(OUTPUT_DISPOSITION_INLINE) &&
      !dvOutputDisposition.equalsIgnoreCase(OUTPUT_DISPOSITION_ATTACHMENT)) {
      throw new Exception(logException(null, "dvOutputDisposition invalid",
        new StringBuffer(SB_SIZE).append(
          "When present the dvOutputDisposition parameter must be one ").append(
            "of the following values: \"").append(
            OUTPUT_DISPOSITION_INLINE).append("\", \"").append(
            OUTPUT_DISPOSITION_ATTACHMENT).append("\"").toString()));
    }

    // Validate dvDataSourceType.
    if (!dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_MANUAL) &&
      !dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_CREATE) &&
      !dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_MANAGED)) {
      throw new Exception(logException(null, "dvDataSourceType invalid",
        new StringBuffer(SB_SIZE).append(
          "When present the dvDataSourceType parameter must be one ").append(
            "of the following values: \"").append(
            DATASOURCE_TYPE_MANUAL).append("\", \"").append(
            DATASOURCE_TYPE_MANUAL).append("\", \"").append(
            DATASOURCE_TYPE_MANUAL).append("\"").toString()));
    }

    // Validate dvDelimiter.
    if (dvDelimiter.length() != 1) {
      throw new Exception(logException(null, "dvDelimiter invalid",
        "dvDelimiter must be a single character in length"));
    }

    // Check for dvDriverClass and dvConnString when
    // dvDateSource==DATASOURCE_TYPE_CREATE.
    if (dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_CREATE)) {
      if (dvDriverClass == null || dvConnString == null) {
        throw new Exception(logException(null, "Missing required parameter",
          "When dvDataSourceType is DATASOURCE_TYPE_CREATE the " +
          "dvDriverClass and dvConnString parameters are required"));
      }
    }

    // Check for dvJNDIName when dvDateSource==DATASOURCE_TYPE_MANAGED.
    if (dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_MANAGED)) {
      if (dvJNDIName == null) {
        throw new Exception(logException(null, "Missing required parameter",
          "When dvDataSourceType is DATASOURCE_TYPE_MANAGED the dvJNDIName " +
          "parameter is required"));
      }
    }

    log.trace("validateParameters() exit");

  } // End validateParameters().


  /**
   * This method is called to get an instance of a JDBC DataSource as specified
   * for the Result.
   *
   * @param  inStack   The ValueStack currently in play.
   * @return           An instance of a JDBC DataSource the Report will use.
   * @throws Exception If anything goes wrong.
   */
  protected DataSource getDataSource(final ValueStack inStack)
    throws Exception {

    log.trace("getDataSource() entry");

    DataSource ds = null;

    // Get DataSource manually.
    if (dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_MANUAL)) {
      try {
        log.debug("dvDataSourceType==DATASOURCE_TYPE_MANUAL");
        ds = (DataSource)inStack.findValue("dvDataSource");
      } catch (final Throwable t) {
        throw new Exception(logException(t,
          "Unable to get DATASOURCE_TYPE_MANUAL DataSource from ValueStack",
          "Check DataSource-related parameters on Result"));
      }
    // Create the data source.
    } else if (dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_CREATE)) {
      try {
        log.debug("dvDataSourceType==DATASOURCE_TYPE_CREATE");
        ds = new DriverManagerDataSource();
        ((DriverManagerDataSource)ds).setDriverClassName(dvDriverClass);
        ((DriverManagerDataSource)ds).setUrl(dvConnString);
        ((DriverManagerDataSource)ds).setUsername(dvUsername);
        ((DriverManagerDataSource)ds).setPassword(dvPassword);
      } catch (final Throwable t) {
        throw new Exception(logException(t,
          "Unable to DATASOURCE_TYPE_CREATE DataSource",
          "Check DataSource-related parameters on Result"));
      }
    // Get data source from container.
    } else if (dvDataSourceType.equalsIgnoreCase(DATASOURCE_TYPE_MANAGED)) {
      try {
        log.debug("dvDataSourceType==DATASOURCE_TYPE_MANAGED");
        InitialContext jndiCntx = new InitialContext();
        Context ctx = null;
        if (dvJNDIContext == null || dvJNDIContext.equalsIgnoreCase("")) {
          ctx = jndiCntx;
        } else {
          ctx = (Context)jndiCntx.lookup(dvJNDIContext);
        }
        ds = (DataSource)ctx.lookup(dvJNDIName);
      } catch (final Throwable t) {
        throw new Exception(logException(t,
          "Unable to get DATASOURCE_TYPE_MANAGED DataSource from container",
          "This is most often a simple typo or a missing dvJNDIContext"));
      }
    }

    // If we don't have a DataSource at this point, we're borked.
    if (ds == null) {
      throw new Exception(logException(null,
        "DataSource null (could not get or create)",
        "See previous messages for precise cause and suggested solution"));
    }

    log.trace("getDataSource() exit");
    return ds;

  } // End getDataSource().


  /**
   * This method creates and does basic setup for the DataVision Report object
   * that will be used to generate the output.
   *
   * @param  inDataSource     The JDBC DataSource the Report will use.
   * @param  inResponse       The HTTPServletRequest object for this request.
   * @param  inServletContext The ServletContext for this request.
   * @param  inOutputStream   The output stream the layout engine will write to.
   * @return                  The configured instance of a DataVision Report
   *                          object that will generate the output.
   * @throws Exception        If anything goes wrong.
   */
  protected Report createReportObject(final DataSource inDataSource,
    final HttpServletResponse inResponse, final ServletContext inServletContext,
    final OutputStream inOutputStream)
    throws Exception {

    log.trace("createReportObject() entry");

    // Get InputStream to report template.
    InputStream reportInputStream =
      inServletContext.getResourceAsStream(dvTemplate);
    if (reportInputStream == null) {
      throw new Exception(logException(null,
        "Could not get stream to template file",
        "Is the path specified by the dvTemplate parameter correct?"));
    }

    // Create Report object and set it up.
    Report report = null;
    try {
      log.debug("Creating Report");
      report = new Report();
      report.parametersSetManually(true);
      report.setDatabaseConnection(inDataSource.getConnection());
    } catch (final Throwable t) {
      throw new Exception(logException(t,
        "Problem instantiating Report or setting database connection",
        "See stack trace that follows to determine root cause of problem"));
    }
    if (report == null) {
      throw new Exception(logException(null,
        "Report object is null",
        "See previous messages for precise cause and suggested solution"));
    }
    // Read in template.
    try {
      log.debug("Reading report template");
      report.read(new InputSource(reportInputStream));
    } catch (final Throwable t) {
      throw new Exception(logException(t, "Problem reading report",
        "See stack trace that follows to determine root cause of problem"));
    }
    // Set layout engine.
    try {
      if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_HTML)) {
        log.debug("Creating HTML layout engine");
        report.setLayoutEngine(new HTMLLE(new PrintWriter(inOutputStream)));
        inResponse.setContentType("text/html");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_PDF)) {
        log.debug("Creating PDF layout engine");
        report.setLayoutEngine(new PDFLE(inOutputStream));
        inResponse.setContentType("application/octet-stream");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_EXCEL)) {
        log.debug("Creating Excel layout engine");
        report.setLayoutEngine(new ExcelLE(inOutputStream, true));
        inResponse.setContentType("application/vnd.ms-excel");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DOCBOOK)) {
        log.debug("Creating DOCBOOK layout engine");
        report.setLayoutEngine(new DocBookLE(new PrintWriter(inOutputStream)));
        inResponse.setContentType("application/docbook+xml");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_DELIMITED)) {
        log.debug("Creating DELIMITED layout engine");
        report.setLayoutEngine(new CharSepLE(new PrintWriter(
          inOutputStream), dvDelimiter.charAt(0)));
        inResponse.setContentType("text/text");
      } else if (dvOutputType.equalsIgnoreCase(OUTPUT_TYPE_LATEX)) {
        log.debug("Creating LATEX layout engine");
        report.setLayoutEngine(new LaTeXLE(new PrintWriter(inOutputStream)));
        inResponse.setContentType("application/x-latex");
      }
    } catch (final Throwable t) {
      throw new Exception(logException(t,
        "Problem creating layout engine (no OutputStream on response?)",
        "See stack trace that follows to determine root cause of problem"));
    }
    log.trace("createReportObject() exit");
    return report;

  } // End createReportObject().


  /**
   * This method populates the Report objects' Parameters with the parameters
   * specified in the Result's config.
   *
   * @param  inStack   The ValueStack currently in play.
   * @param  inReport  The DataVision Report object instance that is to be
   *                   populated.
   * @throws Exception If anything goes wrong.
   */
  protected void populateReportParameters(final ValueStack inStack,
    final Report inReport) throws Exception  {

    log.trace("populateReportParameters() entry");

    Map params = null;
    // If there are parameters specified, populate them.
    if (dvParameters != null) {
      log.debug("Setting parameters on report");
      try {
        params = new HashMap();
        StringTokenizer st = new StringTokenizer(dvParameters, ",");
        // For each parameter specified, populate it.
        while (st.hasMoreTokens()) {
          // Get parameter name and value and log it.
          String nextParameter = st.nextToken();
          Object paramValue = inStack.findValue(nextParameter);
          if (log.isDebugEnabled()) {
            log.debug("Parameter: " + nextParameter + " = " +  paramValue);
          }
          // Set it on the report.
          Parameter p = inReport.findParameterByName(nextParameter);
          p.setValue(0, paramValue);
        }
      } catch (final Throwable t) {
        throw new Exception(logException(t,
          "Exception occurred setting parameter",
          "See previous message and stack trace to determine cause"));
      }
      log.debug("All parameters set");
    }

    log.trace("populateReportParameters() exit");

  } // End populateReportParameters().


  /**
   * This method is called any time an Exception is caught.  It does logging
   * in a consistent manner so that, hopefully, our log messages are useful.
   *
   * @param inThrowable       The Throwable (usually Exception) that we're
   *                          handling.
   * @param inMessage         The basic message to be logged.  This is usually
   *                          a brief description of what went wrong.
   * @param inExtendedMessage The second "extended" portion of the message.
   *                          This is usually a more detailed description of
   *                          the problem, including suggested solution.
   * @return                  The final constructed string form of the message
   *                          to be logged.
   */
  protected String logException(final Throwable inThrowable,
    final String inMessage, final String inExtendedMessage) {

    log.trace("logException() entry");

    // Get stack trace for output, if we got a Throwable.
    ByteArrayOutputStream baos = null;
    if (inThrowable != null) {
      baos = new ByteArrayOutputStream();
      inThrowable.printStackTrace(new PrintStream(baos));
    }

    // Construct log message and output it as an error.
    StringBuffer logMessage = new StringBuffer(SB_SIZE).append(
      inMessage).append("! Aborting ... ").append(
        inExtendedMessage).append(" ... ");
    if (inThrowable != null) {
      logMessage.append(
        "Full stack trace of root exception follows ... ").append(baos);
    }

    log.error(logMessage.toString());
    log.trace("logException() exit");
    return logMessage.toString();

  } // End logException().


} // End class.
